﻿//==============================================================================
// File: MetaPropertyManager.cs
// Created: 2010-05-10
// Author: Piotr Włodek
//==============================================================================
// This file is a part of MAMMOTH project.
// Copyright (C) 2010 AGH University of Science and Technology, Krakow.
// https://caribou.iisg.agh.edu.pl/trac/mammoth
//
// Mammoth is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Mammoth is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with WpfRcp. If not, see http://www.gnu.org/licenses/.
//==============================================================================

using System;
using System.Collections.Generic;
using System.Diagnostics;
using WpfRcp.PropertyModel.Scope;
using System.Linq;
using Res = WpfRcp.PropertyModel.Properties.Resources;

namespace WpfRcp.PropertyModel.Core
{
    /// <summary>
    /// Default implementation of the <see cref="IMetaPropertyManager"/> interface.
    /// </summary>
    [Serializable]
    public class MetaPropertyManager : MetaPropertyManagerBase
    {
        /// <summary>
        /// Stores permanent and transient properties' values and metaproperties.
        /// </summary>
        private readonly IProperty[] m_Properties;

        /// <summary>
        /// Stores dynamic and attached properties' values and metaproperties.
        /// This property is lazy loaded!
        /// </summary>
        private IDictionary<MetaProperty, IProperty> m_DynamicProperties;

        #region Public Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="MetaPropertyManager"/> class.
        /// </summary>
        /// <param name="propertyContainer">An instance of a <see cref="IPropertyContainer"/>.</param>
        public MetaPropertyManager(IPropertyContainer propertyContainer) : base(propertyContainer)
        {
            var propertyContainerType = propertyContainer.GetType();
            var properties = PropertyManager.InitializeStaticProperties(propertyContainerType);
            
            m_Properties = new IProperty[properties.Count()];

            // Assigne defaults
            foreach (var metaProperty in properties)
            {
                PropertyMetadata metadata = metaProperty.GetMetadata(propertyContainerType);

                // ReSharper disable DoNotCallOverridableMethodsInConstructor
                m_Properties[metaProperty.Index] = CreateProperty(metaProperty, metadata);
                // ReSharper restore DoNotCallOverridableMethodsInConstructor
            }
        }

        #endregion

        #region Public Property Management

        protected override IEnumerable<MetaProperty> EnumerateMetaProperties()
        {
            foreach (var property in m_Properties)
            {
                yield return property.MetaProperty;
            }

            if (m_DynamicProperties != null)
            {
                foreach (var metaProperty in DynamicProperties.Keys)
                {
                    yield return metaProperty;
                }
            }
        }

        public override MetaPropertyCollection GetDynamicMetaProperties()
        {
            if (m_DynamicProperties == null)
                return new MetaPropertyCollection();
            
            return new MetaPropertyCollection(DynamicProperties.Keys);
        }

        public override void AddDynamicProperty(MetaProperty metaProperty)
        {
            if (metaProperty == null)
                throw new ArgumentNullException("metaProperty");

            if (metaProperty.PropertyKind == MetaPropertyKind.Permanent ||
                metaProperty.PropertyKind == MetaPropertyKind.Transient)
                throw new ArgumentException("Given metaProperty is not dynamic.");

            try
            {
                if (GetMetaProperties().Contains(metaProperty, MetaPropertyComparer.NameComparer))
                    throw new Exception(string.Format(Res.ExceptionNameAlreadyExists, metaProperty.Name));

                PropertyMetadata metadata = metaProperty.GetMetadata(null);
                IProperty data = CreateProperty(metaProperty, metadata);

                DynamicProperties.Add(metaProperty, data);
            }
            catch (Exception ex)
            {
                throw new MetaPropertyException(string.Format(Res.ExceptionDynamicRegistrationFailed, metaProperty.Name), ex);
            }
        }

        public override void RemoveDynamicProperty(MetaProperty metaProperty)
        {
            if (metaProperty == null)
                throw new ArgumentNullException("metaProperty");

            if (metaProperty.PropertyKind == MetaPropertyKind.Permanent ||
                metaProperty.PropertyKind == MetaPropertyKind.Transient)
                throw new ArgumentException("metaProperty");

            if (!DynamicProperties.ContainsKey(metaProperty))
                throw new MetaPropertyException(string.Format(Res.ExceptionPropertyNotFound, metaProperty.Name));

            try
            {
                bool removed = DynamicProperties.Remove(metaProperty);
                Debug.Assert(removed);
            }
            catch (Exception ex)
            {
                throw new MetaPropertyException(
                    string.Format(Res.ExceptionDynamicUnregistrationFailed, metaProperty.Name), ex);
            }
        }

        #endregion

        #region Public Property Scope Management

        public override void AddPropertyScope(IPropertyScope scope)
        {
            if (scope == null)
                throw new ArgumentNullException("scope");

            foreach (var property in scope.GetDynamicProperties(PropertyContainer))
            {
                AddDynamicProperty(property);
            }

            var notifiableScope = scope as INotifyScopeChanged;
            if (notifiableScope != null) 
            {
                notifiableScope.ScopeChanged += ScopeChangedHandler;
            }
        }

        public override void RemovePropertyScope(IPropertyScope scope)
        {
            if (scope == null)
                throw new ArgumentNullException("scope");

            foreach (var property in scope.GetDynamicProperties(PropertyContainer))
            {
                RemoveDynamicProperty(property);
            }

            var notifiableScope = scope as INotifyScopeChanged;
            if (notifiableScope != null)
            {
                notifiableScope.ScopeChanged -= ScopeChangedHandler;
            }
        }

        private void ScopeChangedHandler(object sender, ScopeChangedEventArgs e)
        {
            if (typeof(PropertyScopeBase).IsAssignableFrom(e.RegistrationType) ||
                e.RegistrationType.IsAssignableFrom(PropertyContainer.GetType()))
            {
                if (e.NewProperty != null)
                    AddDynamicProperty(e.NewProperty);

                if (e.OldProperty != null)
                    RemoveDynamicProperty(e.OldProperty);
            }
        }

        #endregion

        #region Public GetProperty Implementation

        public override IEnumerable<IProperty> GetProperties()
        {
            foreach (var property in m_Properties)
            {
                yield return property;
            }

            if (m_DynamicProperties != null)
            {
                foreach (var property in DynamicProperties.Values)
                {
                    yield return property;
                }
            }
        }

        public override object GetPropertyValue(MetaProperty metaProperty)
        {
            if (metaProperty == null)
                throw new ArgumentNullException("metaProperty");

            if (metaProperty.PropertyKind == MetaPropertyKind.Attached)
            {
                IProperty attachedProperty = GetAttachedPropertyForRead(metaProperty);
                if (attachedProperty != null)
                {
                    return attachedProperty.Value;
                }

                var metadata = metaProperty.GetMetadata(metaProperty.OwnerType);
                return GetDefaultValue(metaProperty, metadata);
            }

            return GetProperty(metaProperty).Value;
        }

        public override IProperty GetProperty(MetaProperty property)
        {
            if (property == null)
                throw new ArgumentNullException("property");

            switch (property.PropertyKind)
            {
                case MetaPropertyKind.Permanent :
                    EnsureStaticProperty(property);
                    return GetPermanentProperty(property);

                case MetaPropertyKind.Transient:
                    EnsureStaticProperty(property);
                    return GetTransientProperty(property);

                case MetaPropertyKind.Dynamic :
                    return GetDynamicProperty(property);

                case MetaPropertyKind.Attached:
                    return GetAttachedProperty(property);

                default:
                    return null;
            }
        }

        private IProperty GetPermanentProperty(MetaProperty property)
        {
            Debug.Assert(property.PropertyKind == MetaPropertyKind.Permanent);

            try
            {
                return m_Properties[property.Index];
            }
            catch (IndexOutOfRangeException ex)
            {
                throw new MetaPropertyException(string.Format(Res.ExceptionPropertyNotFound, property.Name), ex);
            }
        }

        private IProperty GetDynamicProperty(MetaProperty property)
        {
            Debug.Assert(property.PropertyKind == MetaPropertyKind.Dynamic);

            try
            {
                return DynamicProperties[property];
            }
            catch (Exception ex)
            {
                throw new MetaPropertyException(string.Format(Res.ExceptionPropertyNotFound, property.Name), ex);
            }
        }

        private IProperty GetAttachedPropertyForRead(MetaProperty property)
        {
            Debug.Assert(property.PropertyKind == MetaPropertyKind.Attached);

            if (DynamicProperties.ContainsKey(property))
            {
                return DynamicProperties[property];
            }

            return null;
        }

        private IProperty GetAttachedProperty(MetaProperty property)
        {
            Debug.Assert(property.PropertyKind == MetaPropertyKind.Attached);

            try
            {
                if (DynamicProperties.ContainsKey(property) == false)
                    AddDynamicProperty(property);

                return DynamicProperties[property];
            }
            catch(MetaPropertyException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new MetaPropertyException(string.Format(Res.ExceptionPropertyNotFound, property.Name), ex);
            }
        }

        private IProperty GetTransientProperty(MetaProperty property)
        {
            Debug.Assert(property.PropertyKind == MetaPropertyKind.Transient);

            try
            {
                return m_Properties[property.Index];
            }
            catch (IndexOutOfRangeException ex)
            {
                throw new MetaPropertyException(string.Format(Res.ExceptionPropertyNotFound, property.Name), ex);
            }
        }

        #endregion

        #region Protected Properties

        /// <summary>
        /// Gets internal collection of dynamic properties. This is a lazy loaded collection
        /// that is not thread safe.
        /// </summary>
        private IDictionary<MetaProperty, IProperty> DynamicProperties
        {
            get
            {
                if (m_DynamicProperties == null)
                    m_DynamicProperties = new Dictionary<MetaProperty, IProperty>();

                return m_DynamicProperties;
            }
        }

        #endregion

        #region Private Validation

        [Conditional("DEBUG")]
        private void EnsureStaticProperty(MetaProperty property)
        {
            Debug.Assert(property != null);

            if (property.PropertyKind == MetaPropertyKind.Permanent ||
                property.PropertyKind == MetaPropertyKind.Transient)
            {
                foreach (var prop in m_Properties)
                {
                    if (prop.MetaProperty.Equals(property))
                        return;
                }

                throw new MetaPropertyException(string.Format(Res.ExceptionPropertyNotFound, property.Name));
            }
        }

        #endregion
    }
}